package fit.decorator.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;

import fit.decorator.exceptions.InvalidInputException;

public abstract class DataType {
	private static final String STRING_TYPE = "string";
	private static final String DOUBLE_TYPE = "double";
	private static final String INTEGER_TYPE = "integer";
	private static final String INT_TYPE = "int";
	private static final String DATE_TYPE = "date";

	protected static final DataType INTEGER = new DataType() {
		@Override
		protected Object valueOf(String value) throws Exception {
			return Integer.valueOf(value);
		}

		@Override
		protected String addTo(String originalValue, Object value,
				int numberofTime) {
			int intValue = ((Integer) value).intValue() * numberofTime;
			return String.valueOf(Integer.parseInt(originalValue) + intValue);
		}

		@Override
		public String toString() {
			return "DataType = '" + INT_TYPE + "'";
		}
	};

	protected static final DataType DOUBLE = new DataType() {
		@Override
		protected Object valueOf(String value) throws Exception {
			return Double.valueOf(value);
		}

		@Override
		protected String addTo(String originalValue, Object value,
				int numberofTime) {
			double doubleValue = ((Double) value).doubleValue() * numberofTime;
			double results = Double.parseDouble(originalValue) + doubleValue;
			return String.valueOf(new Double(results).floatValue());
		}

		@Override
		public String toString() {
			return "DataType = '" + DOUBLE_TYPE + "'";
		}
	};

	protected static final DataType STRING = new DataType() {
		@Override
		protected Object valueOf(String value) throws Exception {
			return value;
		}

		@Override
		protected String addTo(String originalValue, Object value,
				int numberofTime) {
			return originalValue + multiply(value.toString(), numberofTime);
		}

		private String multiply(String value, int numberofTime) {
			StringBuffer result = new StringBuffer();
			for (int i = 0; i < numberofTime; ++i) {
				result.append(value);
			}
			return result.toString();
		}

		@Override
		public String toString() {
			return "DataType = '" + STRING_TYPE + "'";
		}
	};

	protected static final DataType DATE = new DataType() {
		private SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");

		@Override
		protected String addTo(String originalValue, Object value,
				int numberofTime) {
			GregorianCalendar gc = new GregorianCalendar();
			Date originalDate;
			try {
				originalDate = dateFormat.parse(originalValue);
				gc.setTime(originalDate);
			} catch (ParseException e) {
				// print stack trace and continue
				e.printStackTrace();
			}
			int totalNnumberOfDaysToAdd = ((Integer) value).intValue()
					* numberofTime;
			gc.add(Calendar.DAY_OF_YEAR, totalNnumberOfDaysToAdd);
			return dateFormat.format(gc.getTime());
		}

		@Override
		protected Object valueOf(String value) throws Exception {
			return Integer.valueOf(value);
		}

		@Override
		public String toString() {
			return "DataType = '" + DATE_TYPE + "'";
		}
	};

	private static final Map<String, DataType> predefinedTypes = new HashMap<String, DataType>();
	private static final Map<String, DataType> userDefinedTypes = new HashMap<String, DataType>();

	static {
		predefinedTypes.put(INT_TYPE, INTEGER);
		predefinedTypes.put(INTEGER_TYPE, INTEGER);
		predefinedTypes.put(DOUBLE_TYPE, DOUBLE);
		predefinedTypes.put(STRING_TYPE, STRING);
		predefinedTypes.put(DATE_TYPE, DATE);
	}

	public Object parse(String value) throws InvalidInputException {
		try {
			return valueOf(value);
		} catch (Exception e) {
			throw new InvalidInputException("value '" + value
					+ "' is not a valid " + toString());
		}
	}

	protected abstract String addTo(String originalValue, Object value,
			int numberofTime);

	protected abstract Object valueOf(String value) throws Exception;

	static DataType instance(String dataType) {
		Object type = getDataType(dataType, predefinedTypes);
		if (type == null) {
			type = getDataType(dataType, userDefinedTypes);
			type = defaultToStringTypeIfNull(type);
		}
		return (DataType) type;
	}

	private static Object defaultToStringTypeIfNull(Object type) {
		if (type == null)
			return STRING;
		return type;
	}

	private static Object getDataType(String dataType,
			Map<String, DataType> types) {
		return types.get(dataType.toLowerCase());
	}

	public static void registerUserDefinedDataTypes(String key,
			DataType dataType) {
		userDefinedTypes.put(key.toLowerCase(), dataType);
	}

	public static void clearUserDefinedDataTypes(String key) {
		userDefinedTypes.remove(key.toLowerCase());
	}

	public static void clearUserDefinedDataTypes() {
		userDefinedTypes.clear();
	}

}
